from common.phx_protocol import *
from common.phx_structs import *
from common.phx_definitions import *
from common.phx_trader_spi import CPhxFtdcTraderSpi
from common.phx_trader_api import CPhxFtdcTraderApi
from test.OrderManager import *
import time
import json
from collections import deque
from test.OrderList import OrderList, OrderInfo, Snapshot
import threading
import copy
import sys
import traceback
import math


class MyClient(CPhxFtdcTraderSpi):
    def __init__(self):
        super().__init__()
        self.serverHost = '106.120.131.90'
        self.serverOrderPort = 9000
        self.serverRtnPort = 9001
        self.serverQryPort = 9002
        self.serverMDPort = 9003
        self.nRequestID = 0
        self.orderRef = 0
        self.m_Token = ''
        self.m_UserID = 64
        self.m_Passwd = 'TN0XzX8H'
        self.m_LoginStatus = [False, False, False, False]
        self.query_status = False
        self.is_any_updated = False
        self.game_status = None
        self.ins2om = {}
        self.ins2index = {}
        self.instruments = []
        self.md_list = []  # array of md deque
        self.inst_num = 0
        self.market_data_updated = []
        self.bg_threads = []
        self.bg_file = None
        self.ubiq_price = 10.00
        self.m_pUserApi = CPhxFtdcTraderApi()

    def reset(self):
        """Reset function after each round"""
        for ins, om in self.ins2om.items():
            om.clear()
            self.md_list[self.ins2index[ins]].clear()
            self.market_data_updated[self.ins2index[ins]] = False

        self.is_any_updated = False

    def next_request_id(self):
        self.nRequestID += 1
        return self.nRequestID

    def next_order_ref(self, order_type=0):
        self.orderRef += 1
        return self.orderRef + order_type * 10000000

    def OnFrontConnected(self):
        print("OnFrontConnected, Start to ReqUserLogin")
        self.ReqUserLogin()

    def ReqUserLogin(self):
        field = CPhxFtdcReqUserLoginField()
        field.UserID = self.m_UserID
        field.Password = self.m_Passwd
        ret = self.m_pUserApi.ReqUserLogin(field, PHX_LINK_TYPE_Order, self.next_request_id())
        print("ReqUserLogin Order (%s:%d) ret=%d" % (self.serverHost, self.serverOrderPort, ret))
        ret = self.m_pUserApi.ReqUserLogin(field, PHX_LINK_TYPE_Qry, self.next_request_id())
        print("ReqUserLogin Qry (%s:%d) ret=%d" % (self.serverHost, self.serverQryPort, ret))
        ret = self.m_pUserApi.ReqUserLogin(field, PHX_LINK_TYPE_Rtn, self.next_request_id())
        print("ReqUserLogin Rtn (%s:%d) ret=%d" % (self.serverHost, self.serverRtnPort, ret))
        ret = self.m_pUserApi.ReqUserLogin(field, PHX_LINK_TYPE_MD, self.next_request_id())
        print("ReqUserLogin MD (%s:%d) ret=%d" % (self.serverHost, self.serverMDPort, ret))

    def OnRspUserLogin(self, pRspUserLogin: CPhxFtdcRspUserLoginField, LinkType, ErrorID, nRequestID):
        print('OnRspUserLogin, data=%s, ErrorID=%d, ErrMsg=%s, nRequestID=%d' % (json.dumps(pRspUserLogin.__dict__), ErrorID, get_server_error(ErrorID), nRequestID))
        if ErrorID == 0:
            self.m_LoginStatus[LinkType] = True
            if pRspUserLogin.MaxOrderLocalID > self.orderRef:
                self.orderRef = pRspUserLogin.MaxOrderLocalID % 10000000 + 1

    def OnRspOrderInsert(self, pInputOrder: CPhxFtdcInputOrderField, ErrorID):
        if ErrorID != 0:
            print('OnErrOrderInsert, ErrorID=%d, ErrMsg=%s, data=%s' % (ErrorID, get_server_error(ErrorID), json.dumps(pInputOrder.__dict__)))
            if pInputOrder.InstrumentID not in self.ins2om:
                return
            om = self.ins2om[pInputOrder.InstrumentID]
            om.on_rsp_order_insert(pInputOrder.OrderLocalID)

    def OnRspOrderAction(self, pInputOrderAction: CPhxFtdcOrderActionField, ErrorID):
        if ErrorID != 0:
            print('OnRspOrderAction, orderRef=%d, ErrorID=%d, ErrMsg=%s' % (pInputOrderAction.OrderLocalID, ErrorID, get_server_error(ErrorID)))

    def OnRspQryTradingAccount(self, pTradingAccount: CPhxFtdcRspClientAccountField, ErrorID, nRequestID, bIsLast):
        print('\n[OnRspQryTradingAccount]')
        if ErrorID == 0:
            data = pTradingAccount.__dict__
            data["TotalMarketMakingUncompleteCount"] = data["TotalMarketMakingCount"] - data["TotalMarketMakingCompleteCount"]
            for key, value in data.items(): 
                print(str(key) + ": " + str(value))
        else:
            print('ErrorID=%d, ErrMsg=%s, bIsLast=%d' % (ErrorID, get_server_error(ErrorID), bIsLast))

        print('\n[UBIQ_price] %.2f' % self.ubiq_price)
        print('[range] %.1f - %.1f' % (math.ceil(self.ubiq_price*9)/10, math.floor(self.ubiq_price*11)/10))
        # print("len(md_list): %d" % len(self.md_list))
        print("[OrderRef] %d" % self.orderRef)
        self.printOrderStatus()
        self.printMarketData()
        sys.stdout.flush()

    def listeningTradingAccount(self):
        last_time = time.time()
        field = CPhxFtdcQryClientAccountField()
        while True:
            t = time.time()
            if t - last_time > 5 and self.m_pUserApi.all_connected:
                last_time = t
                ret = self.m_pUserApi.ReqQryTradingAccount(field, self.next_request_id())
                if not ret:
                    print("ReqQryTradingAccount failed")
            time.sleep(1)

    def listeningUBIQPrice(self):
        while True:
            try:
                self.ubiq_price = self.md_list[-1][-1].LastPrice
            except:
                self.ubiq_price = 10.00
                print("[Exception] Can not get UBIQ price!")
                print(traceback.format_exc())
            finally:
                time.sleep(0.1)

    def OnRspQryInstrument(self, pInstrument: CPhxFtdcRspInstrumentField, ErrorID, nRequestID, bIsLast):
        # print('OnRspQryInstrument, data=%s, ErrorID=%d, bIsLast=%d' % (json.dumps(pInstrument.__dict__), ErrorID, bIsLast))
        if pInstrument.InstrumentID not in self.ins2om:
            self.ins2om[pInstrument.InstrumentID] = OrderManager(pInstrument.InstrumentID)
            self.md_list.append(deque(maxlen=10))
            self.instruments.append(copy.copy(pInstrument))
            self.market_data_updated.append(False)
            self.ins2index[pInstrument.InstrumentID] = self.inst_num
            self.inst_num += 1

        if bIsLast:
            self.query_status = True
            print("total %d instruments" % self.inst_num)

    def OnRtnGameStatus(self, pGameStatus: CPhxFtdcGameStatusField):
        # print('OnRtnGameStatus, data=%s' % json.dumps(pGameStatus.__dict__))
        self.game_status = pGameStatus

    def OnRtnMarketData(self, pMarketData: CPhxFtdcDepthMarketDataField):
        if pMarketData.InstrumentID in self.ins2index:
            # print('OnRtnMarketData, data=%s' % json.dumps(pMarketData.__dict__))
            index = self.ins2index[pMarketData.InstrumentID]
            self.md_list[index].append(pMarketData)
            self.market_data_updated[index] = True
            self.is_any_updated = True

    def OnRtnOrder(self, pOrder: CPhxFtdcOrderField):
        print('OnRtnOrder, data=%s' % json.dumps(pOrder.__dict__))
        if pOrder.InstrumentID not in self.ins2om:
            return
        om = self.ins2om[pOrder.InstrumentID]
        om.on_rtn_order(pOrder)

    def getOrderType(self, OrderLocalID):
        if OrderLocalID // 10000000 == 1:
            return "做市"
        elif OrderLocalID // 10000000 == 2:
            return "盈利"
        else:
            return "其他"

    def OnRtnTrade(self, pTrade: CPhxFtdcTradeField):
        # print('OnRtnTrade, data=%s' % json.dumps(pTrade.__dict__))
        print("[OnRtnTrade][%s] %6.3f %3d  %s  %s  %s" % (pTrade.InstrumentID, pTrade.Price, pTrade.Volume, \
            self.getOrderType(pTrade.OrderLocalID), \
            "Buy " if pTrade.Direction == PHX_FTDC_D_Buy else "Sell", \
            "Open " if pTrade.OffsetFlag == PHX_FTDC_OF_Open else "Close"))
        if pTrade.InstrumentID not in self.ins2om:
            return
        om = self.ins2om[pTrade.InstrumentID]
        om.on_rtn_trade(pTrade)

    def OnErrRtnOrderInsert(self, pInputOrder: CPhxFtdcInputOrderField, ErrorID):
        if ErrorID != 0:
            print('OnErrRtnOrderInsert, ErrorID=%d, ErrMsg=%s, orderRef=%d' % (pInputOrder.ExchangeErrorID, get_server_error(pInputOrder.ExchangeErrorID), pInputOrder.OrderLocalID))
            if pInputOrder.InstrumentID not in self.ins2om:
                return
            om = self.ins2om[pInputOrder.InstrumentID]
            om.on_rsp_order_insert(pInputOrder.OrderLocalID)

    def OnErrRtnOrderAction(self, pOrderAction: CPhxFtdcOrderActionField, ErrorID):
        if ErrorID != 0:
            print('OnErrRtnOrderAction, ErrorID=%d, ErrMsg=%s, orderRef=%d' % (pOrderAction.ExchangeErrorID, get_server_error(pOrderAction.ExchangeErrorID), pOrderAction.OrderLocalID))

    def OnRspQryOrder(self, pOrder: CPhxFtdcOrderField, ErrorID, nRequestID, bIsLast):
        if pOrder is not None and ErrorID == 0:
            if pOrder.InstrumentID not in self.ins2om:
                return
            om = self.ins2om[pOrder.InstrumentID]
            # print("id: %7d, ins_id: %s, price: %6.3f, VolumeTraded: %3d, VolumeTotal: %3d, type: %s %s" % \
            #     (pOrder.OrderLocalID, pOrder.InstrumentID, pOrder.LimitPrice, pOrder.VolumeTraded, pOrder.VolumeTotal, \
            #         "Buy" if pOrder.Direction == PHX_FTDC_D_Buy else "Sell", \
            #         "Open" if pOrder.CombOffsetFlag[0] == PHX_FTDC_OF_Open else "Close"))
            om.insert_init_order(pOrder)
            om.on_rtn_order(pOrder)

        if bIsLast:
            self.query_status = True
            print("init order query over")

    def OnRspQryTrade(self, pTrade: CPhxFtdcTradeField, ErrorID, nRequestID, bIsLast):
        if pTrade is not None and ErrorID == 0:
            if pTrade.InstrumentID not in self.ins2om:
                return
            om = self.ins2om[pTrade.InstrumentID]
            om.on_rtn_trade(pTrade)

        if bIsLast:
            self.query_status = True
            print("init trade query over")

    def timeout_wait(self, timeout, condition=None):
        while timeout > 0:
            time.sleep(1)
            timeout -= 1
            if condition is None:
                if self.query_status:
                    return True
            elif isinstance(condition, list):
                if all(condition):
                    return True
        return False

    def Init(self):
        self.m_pUserApi.RegisterSpi(self)
        self.m_pUserApi.RegisterOrderFront(self.serverHost, self.serverOrderPort)
        self.m_pUserApi.RegisterQryFront(self.serverHost, self.serverQryPort)
        self.m_pUserApi.RegisterRtnFront(self.serverHost, self.serverRtnPort)
        self.m_pUserApi.RegisterMDFront(self.serverHost, self.serverMDPort)

        self.m_pUserApi.Init()
        if not self.timeout_wait(10, self.m_LoginStatus):
            return False

        print("OnRspUserLogin, all link ready")
        self.query_status = False
        ret = self.m_pUserApi.ReqQryInstrument(CPhxFtdcQryInstrumentField(), self.next_request_id())
        if (not ret) or (not self.timeout_wait(10)):
            print("ReqQryInstrument failed")
            return False

        self.query_status = False
        field = CPhxFtdcQryOrderField()
        field.InvestorID = self.m_UserID
        ret = self.m_pUserApi.ReqQryOrder(field, self.next_request_id())
        if (not ret) or (not self.timeout_wait(30)):
            print("ReqQryOrder failed")
            return False

        self.query_status = False
        field = CPhxFtdcQryTradeField()
        field.InvestorID = self.m_UserID
        ret = self.m_pUserApi.ReqQryTrade(field, self.next_request_id())
        if (not ret) or (not self.timeout_wait(30)):
            print("ReqQryTrade failed")
            return False

        print("输出正重定向至文件bg.log中")
        self.bg_file = open("bg.log", "w", encoding="utf-8")
        sys.stdout = self.bg_file
        print("后台线程启动中")
        self.bg_threads.append(threading.Thread(target=self.listeningTradingAccount))
        self.bg_threads.append(threading.Thread(target=self.listeningUBIQPrice))
        for t in self.bg_threads:
            t.start()

        if not self.timeout_wait(10):
            return False
        return True

    def send_input_order(self, order: OrderInfo):
        field = CPhxFtdcQuickInputOrderField()
        field.OrderPriceType = order.OrderPriceType
        field.OffsetFlag = order.OffsetFlag
        field.HedgeFlag = PHX_FTDC_HF_Speculation
        field.InstrumentID = order.InstrumentID
        field.Direction = order.Direction
        field.VolumeTotalOriginal = order.VolumeTotalOriginal
        field.TimeCondition = PHX_FTDC_TC_IOC
        field.VolumeCondition = PHX_FTDC_VC_AV
        if order.OrderPriceType == PHX_FTDC_OPT_LimitPrice:
            field.LimitPrice = order.LimitPrice
        field.OrderLocalID = order.OrderLocalID
        ret = self.m_pUserApi.ReqQuickOrderInsert(field, self.next_request_id())
        # print("QuickOrderInsert ", field, ret)

    def send_cancel_order(self, order: OrderInfo):
        field = CPhxFtdcOrderActionField()
        field.OrderSysID = order.OrderSysID
        field.InvestorID = self.m_UserID
        field.OrderLocalID = order.OrderLocalID
        field.ExchangeErrorID = 0
        ret = self.m_pUserApi.ReqOrderAction(field, self.next_request_id())
        # print("ActionOrder data=%s, ret=%d" % (json.dumps(field.__dict__), ret))

    def getMarketData(self, index):
        try:
            return self.md_list[index][-1]
        except:
            # print("[Exception] Can not get MarketData: %s" % self.instruments[index].InstrumentID)
            # print(self.md_list[index])
            # print(traceback.format_exc())
            return None

    def getPrice(self, index):
        price = -99.99
        try:
            price = self.md_list[index][-1].LastPrice
        except:
            print("[Exception] Can not get Price: %s" % self.instruments[index].InstrumentID)
            # print(traceback.format_exc())
        return price

    def getInstrument(self, index):
        return self.instruments[index]

    def getOrderManager(self, id):
        return self.ins2om[id]

    def getValue(self, ins_index):
        ins = self.instruments[ins_index]
        if ins.ProductID == "UBIQCALL":
            return self.ubiq_price - ins.StrikePrice
        elif ins.ProductID == "UBIQPUT":
            return ins.StrikePrice - self.ubiq_price
        elif ins.ProductID == "UBIQ":
            return self.ubiq_price
        return -99.99

    def getMinSpread(self, md):
        if md:
            if md.AskPrice1 > 0 and md.BidPrice1 > 0:
                return md.AskPrice1 - md.BidPrice1
        return -99.9

    def printOrderStatus(self):
        print('\n[OrderStatus]')
        for ins_id, ins_index in self.ins2index.items():
            om = self.ins2om[ins_id]
            ins = self.instruments[ins_index]
            md = self.getMarketData(ins_index)
            bid_order_num = om.bid_list.size()
            ask_order_num = om.ask_list.size()
            obligationFulfilledFlag, buyingList, sellingList = om.obligationFulfilled()
            # if bid_order_num + ask_order_num > 0:
            if ins.StrikePrice <= self.ubiq_price*1.15 and ins.StrikePrice >= self.ubiq_price*0.85:
                value = self.getValue(ins_index)
                price = self.getPrice(ins_index)
                print("[%s]ProductID: %s  价值: %6.3f  市场价: %6.3f  价值价格差额: %6.3f  最小价差: %5.3f  是否完成做市义务: %s\
                    \n[买单]挂单数: %3d  挂单量: %3d  挂单: %s\
                    \n[卖单]挂单数: %3d  挂单量: %3d  挂单: %s\
                    \n[多头]开仓: %5d  平仓: %5d  可平: %5d  longSnapshot: %s\
                    \n[空头]开仓: %5d  平仓: %5d  可平: %5d  shortSnapshot: %s\
                    \n[挂单量]多头开仓: %3d  多头平仓: %3d  空头开仓: %3d  空头平仓: %3d\n%d %d %d %d" % \
                    (ins_id, ins.ProductID, value, price, value - price, self.getMinSpread(md), str(obligationFulfilledFlag), \
                        om.get_buying_order_num(), om.get_buying_volume(), json.dumps(buyingList), \
                        om.get_selling_order_num(), om.get_selling_volume(), json.dumps(sellingList), \
                        om.longSnapshot.OpenVolume, om.longSnapshot.CloseVolume, om.get_long_position_closeable(), json.dumps(om.longSnapshot.__dict__), \
                        om.shortSnapshot.OpenVolume, om.shortSnapshot.CloseVolume, om.get_short_position_closeable(), json.dumps(om.shortSnapshot.__dict__), \
                        om.get_long_open_trading_volume(), om.get_long_close_trading_volume(), om.get_short_open_trading_volume(), om.get_short_close_trading_volume(), \
                        om.get_buying_volume_by_type(1), om.get_selling_volume_by_type(1), om.get_buying_volume_by_type(2), om.get_selling_volume_by_type(2)))

    def printMarketData(self):
        print('\n[MarketData]')
        for md_deque in self.md_list:
            if md_deque:
                md = md_deque[-1]
                value = self.getValue(self.ins2index[md.InstrumentID])
                print("[%s] value: %6.3f  LastPrice: %6.3f  valueMinusPrice: %6.3f  minSpread: %5.3f  LastVolume: %3d\
                    \n[bid] (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d)\
                    \n[ask] (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d) (%6.3f, %3d)\n" % \
                    (md.InstrumentID, value, md.LastPrice, value - md.LastPrice, self.getMinSpread(md), md.LastVolume, \
                        md.BidPrice1, md.BidVolume1, md.BidPrice2, md.BidVolume2, md.BidPrice3, md.BidVolume3, \
                        md.BidPrice4, md.BidVolume4, md.BidPrice5, md.BidVolume5, \
                        md.AskPrice1, md.AskVolume1, md.AskPrice2, md.AskVolume2, md.AskPrice3, md.AskVolume3, \
                        md.AskPrice4, md.AskVolume4, md.AskPrice5, md.AskVolume5))
                


    def cancel_live_orders(self, om):
        bids, asks = om.get_live_orders()
        for order in [asks, bids]:
            try:
                self.send_cancel_order(order)
            except:
                print("[Exception] Cancel order failed: %s" % om.instrument_id)
            # print(traceback.format_exc())

    def cancel_all_live_orders(self):
        for key, value in self.ins2index.items():
            om = self.ins2om[key]
            self.cancel_live_orders(om)
            time.sleep(0.1)

    def input_order(self, ins_id, order_type=0, direction='0', offset='0', price=10, volume=10):
        om = self.ins2om[ins_id]
        if not order_type:
            self.send_input_order(om.place_market_order(self.next_order_ref(), direction, offset, volume))
        else:
            self.send_input_order(om.place_limit_order(self.next_order_ref(), direction, offset, price, volume))

    def run_strategy(self):
        pass

    def run(self):
        if self.Init():
            print("init success")
            resetted = True
            while True:
                if self.game_status is None or (not self.m_pUserApi.all_connected):
                    print("server not started")
                    time.sleep(1)
                elif self.game_status.GameStatus == 0:
                    print("game not started, waitting for start")
                    time.sleep(1)
                elif self.game_status.GameStatus == 1:
                    resetted = False
                    # self.cancel_all_live_orders()
                    self.run_strategy()
                    time.sleep(0.01)
                elif self.game_status.GameStatus == 2:
                    print("game settling")
                    time.sleep(1)
                elif self.game_status.GameStatus == 3:
                    print("game settled, waiting for next round")
                    if not resetted:
                        self.reset()
                        resetted = True
                        print("self resetted")
                    time.sleep(1)
                elif self.game_status.GameStatus == 4:
                    print("game finished")
                    break
        else:
            print("init failed")


if __name__ == '__main__':
    client = MyClient()
    client.run()

